package com.fun.widget.tag;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.fun.widget.tag.bean.BaseTagStyleBean;
import com.fun.widget.tag.core.ScreenUtils;
import com.fun.widget.tag.core.TagItemView;
import com.fun.widget.tag.core.TagStyleBuilder;
import com.fun.widget.tag.event.OnTagItemClickListener;
import com.fun.widget.tag.event.OnTagItemLongClickListener;
import com.fun.widget.tag.event.OnTagsMeasureEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Created by AdminFun
 * @version v1.0.0
 * @date 2019/10/9 18:48
 */
public class TagsView<Bean extends BaseTagStyleBean> extends ViewGroup {

    private boolean mUnifiedTheme = true;       // 是否统一样式？反之则高度定制样式
    private TagStyleBuilder mStyleBuilder;      // 统一样式构建器，仅当模式为统一样式时有效
    private int mPositionInList = 0;            // 当前的容器ID，用于列表场景记录position
    private int mVerticalInterval = 5;          // 标签之间的纵向间距：默认5dp
    private int mHorizontalInterval = 5;        // 标签之间的横向间距：默认5dp
    private int mMaxLines = 0;                  // 最多显示行数，大于0时生效
    private int mGravity = Gravity.START;       // 标签的排版权重
    private boolean mDragEnable;                // 是否可以拖动排序，默认false
    private float mSensitivity = 1.0f;          // 拖动灵敏度，默认1.0f

    private OnTagItemLongClickListener tagItemLongClickListener;
    private OnTagItemClickListener onTagItemClickListener;
    private OnTagsMeasureEvent mOnTagMeasureListener;
    private boolean isCallbackMeasureListener = false;

    private List<Bean> mTags;
    private List<View> mChildViews;

    /**
     * TagView drag state(default STATE_IDLE)
     */
    private int mTagViewState = ViewDragHelper.STATE_IDLE;
    private Paint mPaint;
    private RectF mRectF;
    private ViewDragHelper mViewDragHelper;
    private int[] mViewPos;
    private int mChildHeight;

    private int childCount = 0, realLines = 0;
    private int widthSpecSize, heightSpecSize, heightSpecMode;

    /**
     * 记录当前标签容器在列表中的position位置，也可以当做是ID，总之它就是一个唯一CODE！
     */
    public void setPosition(int position) {
        this.mPositionInList = position;
    }

    /**
     * 设置标签样式的统一性
     *
     * @param unified 是否统一样式，默认：统一
     */
    public void setUnifiedTheme(boolean unified) {
        this.mUnifiedTheme = unified;
    }

    /**
     * 统一的样式构建器
     */
    public void setStyleBuilder(TagStyleBuilder styleBuilder) {
        this.mStyleBuilder = styleBuilder;
    }

    /**
     * 标签长按事件
     */
    public void setOnTagLongClickListener(OnTagItemLongClickListener onTagLongClickListener) {
        this.tagItemLongClickListener = onTagLongClickListener;
    }

    /**
     * 标签点击事件
     */
    public void setOnTagItemClickListener(OnTagItemClickListener onTagItemClickListener) {
        this.onTagItemClickListener = onTagItemClickListener;
    }

    /**
     * 标签容器计算完成回调
     */
    public void setOnTagMeasureListener(OnTagsMeasureEvent mOnTagMeasureListener) {
        this.mOnTagMeasureListener = mOnTagMeasureListener;
    }

    /**
     * 设置标签集合（此方法必须在所有属性都设置完毕后调用，否则此方法之后设置的属性都将无效）
     *
     * @param tags 标签数组，对象必须继承自{@link BaseTagStyleBean}
     */
    public void setTags(List<Bean> tags) {
        this.isCallbackMeasureListener = false;
        this.mTags = tags;
        this.invalidateTags();
    }

    /**
     * 设置最大行数，如果设置为0则表示不显示行数，大于0则以设置的数量限制行数
     *
     * @param maxLines max line count
     */
    public void setMaxLines(int maxLines) {
        this.mMaxLines = maxLines;
        this.invalidateTags();
    }

    /**
     * Remove a TagView in specified position.
     */
    public void removeTag(int position) {
        this.onRemoveTag(position);
        this.postInvalidate();
    }

    public TagsView(Context context) {
        this(context, null);
    }

    public TagsView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TagsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.TagsView, defStyleAttr, 0);
        mVerticalInterval = (int) attributes.getDimension(R.styleable.TagsView_group_vertical_padding, ScreenUtils.dip2px(context, mVerticalInterval));
        mHorizontalInterval = (int) attributes.getDimension(R.styleable.TagsView_group_horizontal_padding, ScreenUtils.dip2px(context, mHorizontalInterval));
        mDragEnable = attributes.getBoolean(R.styleable.TagsView_group_enable_drag, false);
        mSensitivity = attributes.getFloat(R.styleable.TagsView_group_drag_sensitivity, mSensitivity);
        mGravity = attributes.getInt(R.styleable.TagsView_group_gravity, mGravity);
        mMaxLines = attributes.getInt(R.styleable.TagsView_group_max_lines, mMaxLines);
        mUnifiedTheme = attributes.getBoolean(R.styleable.TagsView_group_unified_theme, true);
        attributes.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRectF = new RectF();
        mChildViews = new ArrayList<>();
        mViewDragHelper = ViewDragHelper.create(this, mSensitivity, new TagsView.DragHelperCallBack());
        setWillNotDraw(false);
    }

    private int getRealLine() {
        int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        int lines = 1;
        for (int i = 0, curLineW = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            int dis = childView.getMeasuredWidth() + mHorizontalInterval;
            int height = childView.getMeasuredHeight();
            mChildHeight = i == 0 ? height : Math.min(mChildHeight, height);
            curLineW += dis;
            if (curLineW - mHorizontalInterval > availableW) {
                lines++;
                curLineW = dis;
            }
        }
        return lines;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        this.childCount = getChildCount();
        this.realLines = this.getRealLine();
        this.widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        this.heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        this.heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if (mOnTagMeasureListener != null && !isCallbackMeasureListener) {
            this.isCallbackMeasureListener = true;
            this.mOnTagMeasureListener.onTagLayoutMeasured(mPositionInList, childCount, realLines);
        }
        this.resetWidthAndHeightDimension();
    }

    private void resetWidthAndHeightDimension() {
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (heightSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.UNSPECIFIED) {
            int disLines = (mMaxLines <= 0 ? realLines : mMaxLines);
            int reHeight = (mVerticalInterval + mChildHeight) * disLines
                    - mVerticalInterval + getPaddingTop() + getPaddingBottom();
            setMeasuredDimension(widthSpecSize, reHeight);
        } else {
            setMeasuredDimension(widthSpecSize, heightSpecSize);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRectF.set(0, 0, w, h);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int childCount = getChildCount();
        if (childCount <= 0) {
            return;
        }

        int availableW = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        int curRight = getMeasuredWidth() - getPaddingRight();
        int curTop = getPaddingTop();
        int curLeft = getPaddingLeft();
        int sPos = 0;
        mViewPos = new int[childCount * 2];

        for (int i = 0; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                int width = childView.getMeasuredWidth();
                if (mGravity == Gravity.RIGHT || mGravity == Gravity.END) {
                    if (curRight - width < getPaddingLeft()) {
                        curRight = getMeasuredWidth() - getPaddingRight();
                        curTop += mChildHeight + mVerticalInterval;
                    }
                    mViewPos[i * 2] = curRight - width;
                    mViewPos[i * 2 + 1] = curTop;
                    curRight -= width + mHorizontalInterval;

                } else if (mGravity == Gravity.CENTER) {
                    if (curLeft + width - getPaddingLeft() > availableW) {
                        int leftW = getMeasuredWidth() - mViewPos[(i - 1) * 2]
                                - getChildAt(i - 1).getMeasuredWidth() - getPaddingRight();
                        for (int j = sPos; j < i; j++) {
                            mViewPos[j * 2] = mViewPos[j * 2] + leftW / 2;
                        }
                        sPos = i;
                        curLeft = getPaddingLeft();
                        curTop += mChildHeight + mVerticalInterval;
                    }
                    mViewPos[i * 2] = curLeft;
                    mViewPos[i * 2 + 1] = curTop;
                    curLeft += width + mHorizontalInterval;
                    if (i == childCount - 1) {
                        int leftW = getMeasuredWidth() - mViewPos[i * 2]
                                - childView.getMeasuredWidth() - getPaddingRight();
                        for (int j = sPos; j < childCount; j++) {
                            mViewPos[j * 2] = mViewPos[j * 2] + leftW / 2;
                        }
                    }

                } else {
                    if (curLeft + width - getPaddingLeft() > availableW) {
                        curLeft = getPaddingLeft();
                        curTop += mChildHeight + mVerticalInterval;
                    }
                    mViewPos[i * 2] = curLeft;
                    mViewPos[i * 2 + 1] = curTop;
                    curLeft += width + mHorizontalInterval;
                }
            }
        }

        // layout all child views
        for (int i = 0; i < mViewPos.length / 2; i++) {
            View childView = getChildAt(i);
            childView.layout(mViewPos[i * 2],
                    mViewPos[i * 2 + 1],
                    mViewPos[i * 2] + childView.getMeasuredWidth(),
                    mViewPos[i * 2 + 1] + mChildHeight);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mViewDragHelper.continueSettling(true)) {
            requestLayout();
        }
    }

    /**
     * Remove all TagViews.
     */
    public void removeAllTags() {
        if (mChildViews != null) {
            this.mChildViews.clear();
        }
        this.removeAllViews();
        this.postInvalidate();
    }

    /**
     * 重新绘制标签视图
     */
    private void invalidateTags() {
        this.removeAllTags();
        if (mTags == null || mTags.isEmpty()) {
            return;
        }
        final int tagSize = mTags.size();
        for (int i = 0; i < tagSize; i++) {
            this.addTag2Groups(mTags.get(i), mChildViews.size());
        }
        this.postInvalidate();
    }

    /**
     * 添加1个标签视图到容器中
     *
     * @param info     标签对象
     * @param position 位置
     */
    private void addTag2Groups(@NonNull Bean info, int position) {
        if (position < 0 || position > mChildViews.size()) {
            throw new RuntimeException("当前需要绘制的标签位置不正确！");
        }
        TagItemView tagView = new TagItemView(getContext());
        tagView.setText(info.getText());

        if (mUnifiedTheme) {
            if (mStyleBuilder == null) {
                throw new NullPointerException("还未设置统一的标签样式对象！");
            }
            tagView.setStyleBuilder(mStyleBuilder);
        } else {
            if (info.getStyleBuilder() == null) {
                throw new NullPointerException("每一个标签都必须设定一个样式对象！");
            }
            tagView.setStyleBuilder(info.getStyleBuilder());
        }
        tagView.setCurrentParentId(mPositionInList);
        tagView.setOnTagItemClickListener(onTagItemClickListener);
        tagView.setOnTagItemLongClickListener(tagItemLongClickListener);
        this.mChildViews.add(position, tagView);

        if (position < mChildViews.size()) {
            for (int i = position; i < mChildViews.size(); i++) {
                mChildViews.get(i).setTag(i);
            }
        } else {
            tagView.setTag(position);
        }
        this.addView(tagView, position);
    }

    /**
     * 移除一个标签视图
     *
     * @param position 需要移除的标签位置
     */
    private void onRemoveTag(int position) {
        if (position < 0 || position >= mChildViews.size()) {
            throw new RuntimeException("Illegal position!");
        }
        this.mChildViews.remove(position);
        this.removeViewAt(position);
        for (int i = position; i < mChildViews.size(); i++) {
            this.mChildViews.get(i).setTag(i);
        }
    }

    /**
     * Get current drag view state.
     */
    public int getTagViewState() {
        return mTagViewState;
    }

    /**
     * Return number of child tags
     *
     * @return size
     */
    public int size() {
        return mChildViews == null ? 0 : mChildViews.size();
    }

    /******************************** 拖动相关事件和方法 ******************************************/

    private class DragHelperCallBack extends ViewDragHelper.Callback {

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            mTagViewState = state;
        }

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            requestDisallowInterceptTouchEvent(true);
            return mDragEnable;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            final int leftX = getPaddingLeft();
            final int rightX = getWidth() - child.getWidth() - getPaddingRight();
            return Math.min(Math.max(left, leftX), rightX);
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            final int topY = getPaddingTop();
            final int bottomY = getHeight() - child.getHeight() - getPaddingBottom();
            return Math.min(Math.max(top, topY), bottomY);
        }

        @Override
        public int getViewHorizontalDragRange(@NonNull View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        @Override
        public int getViewVerticalDragRange(@NonNull View child) {
            return getMeasuredHeight() - child.getMeasuredHeight();
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            requestDisallowInterceptTouchEvent(false);
            int[] pos = onGetNewPosition(releasedChild);
            int posRefer = onGetCoordinateReferPos(pos[0], pos[1]);
            onChangeView(releasedChild, posRefer, (int) releasedChild.getTag());
            mViewDragHelper.settleCapturedViewAt(pos[0], pos[1]);
            invalidate();
        }
    }

    private int[] onGetNewPosition(View view) {
        int left = view.getLeft();
        int top = view.getTop();
        int bestMatchLeft = mViewPos[(int) view.getTag() * 2];
        int bestMatchTop = mViewPos[(int) view.getTag() * 2 + 1];
        int tmpTopDis = Math.abs(top - bestMatchTop);
        for (int i = 0; i < mViewPos.length / 2; i++) {
            if (Math.abs(top - mViewPos[i * 2 + 1]) < tmpTopDis) {
                bestMatchTop = mViewPos[i * 2 + 1];
                tmpTopDis = Math.abs(top - mViewPos[i * 2 + 1]);
            }
        }
        int rowChildCount = 0;
        int tmpLeftDis = 0;
        for (int i = 0; i < mViewPos.length / 2; i++) {
            if (mViewPos[i * 2 + 1] == bestMatchTop) {
                if (rowChildCount == 0) {
                    bestMatchLeft = mViewPos[i * 2];
                    tmpLeftDis = Math.abs(left - bestMatchLeft);
                } else {
                    if (Math.abs(left - mViewPos[i * 2]) < tmpLeftDis) {
                        bestMatchLeft = mViewPos[i * 2];
                        tmpLeftDis = Math.abs(left - bestMatchLeft);
                    }
                }
                rowChildCount++;
            }
        }
        return new int[]{bestMatchLeft, bestMatchTop};
    }

    private int onGetCoordinateReferPos(int left, int top) {
        int position = 0;
        for (int i = 0; i < mViewPos.length / 2; i++) {
            if (left == mViewPos[i * 2] && top == mViewPos[i * 2 + 1]) {
                position = i;
            }
        }
        return position;
    }

    private void onChangeView(View view, int newPos, int originPos) {
        mChildViews.remove(originPos);
        mChildViews.add(newPos, view);
        for (View child : mChildViews) {
            child.setTag(mChildViews.indexOf(child));
        }
        removeViewAt(originPos);
        addView(view, newPos);
    }
}